#include <stdio.h>
#include <string.h>
#define HAVE_STDINT
#include <windows.h>  
#include <web/webserver.h>
#include <deelx.h>
#include <string>
#include <vector>
#include <assert.h>
#include <multiutils.h>
#include <strutil.h>
#define KVUTILS_DO_IMPLEMENTATION
#include <fileutil.h>
#include "utils.h"
#include "triindex.h"
#include <deelx.h>

std::vector<std::string> indexDirArray;
std::vector<TriIndex> indexArray;

/**
 * @brief The MyWebServer class
 */
class MyWebServer: public WebServer
{
    bool    _wantExit;
public:
    MyWebServer(const char* documentRoot, const char* ports):_wantExit(false) { SetDocumentRoot(documentRoot); SetPorts(ports); }
    bool wantToExit()
    {
        return _wantExit;
    }
    //virtual
    bool OnRequest(ExecContext& ctx);
};
/**
 * @brief getExtracts scan a text file four for strings matching a pattern
 * @param path source file to get code lines
 * @param pattern the pattern tobe tested against
 * @param extracts the resulting array of extracts
 * @param extractSize the max number of extracts the array can hold
 * @return the number of extracts found, always inferior to extractSize
 */
struct _extract {
    int line;
    char text[1024];
};

int getExtracts(const char* path, const char* pattern, _extract* extracts, size_t extractSize)
{
    FILE* fi = fopen(path, "rt");
    if (!fi)
        return 0;
    char buffer[1024];
    int numLine = 1;
    CRegexpT <char> regexp(pattern, IGNORECASE);
    int numExtract = 0;
    while (fgets(buffer, sizeof(buffer), fi)!=NULL)
    {
        // find first
        if (numExtract>=extractSize)
            break;
        const char*ptr = buffer;
        bool firstMatch = true;
        SStr dest(extracts[numExtract].text, 1024);
        dest.Set("");
        MatchResult result = regexp.Match(buffer);
        int index = 0;
        while(result.IsMatched())
        {
            if (firstMatch)
                extracts[numExtract].line = numLine;            
            const char prefix[]="<strong>"; //"<span class=\"matched\">
            const char suffix[]="</strong>"; //</span>
            if (index>80)
                break;
            // print before match
            size_t len = result.GetStart() - index;
            const char* ptr = buffer + index;
            if (len > 32)
            {
                ptr += len - 32;
                len = 32;
                dest.Append("...");
            }
            dest.FormatAppend("%.*s", len, ptr);
            // output decorated matching string
            dest.FormatAppend("%s%.*s%s", prefix, result.GetEnd() - result.GetStart(), buffer + result.GetStart(), suffix);
            index = result.GetEnd();

            // find next
            result = regexp.Match(buffer, result.GetEnd());
            firstMatch = false;
        }
        if (!firstMatch)
        {
            //end of text
            size_t len = dest.Length();
            if (len<80)
            {
                size_t remains = strlen(buffer+index);
                if ((len + remains)>80)
                {
                    remains = 80-len;
                    dest.AppendN(buffer+index, remains);
                    dest.Append("...");
                }
                else
                    dest.AppendN(buffer+index, remains);
            }
            else
                dest.Append("...");
            dest.Replace("\\","\\\\");
            dest.Replace("/","\\/");
            dest.Replace("\"","\\\""); //order is important, \ to \\ has to be done first: else \ will be doubled
            dest.Replace("\b","\\b");
            dest.Replace("\f","\\f");
            dest.Replace("\n","\\n");
            dest.Replace("\r","\\r");
            dest.Replace("\t","\\t");
            //not sure about this one: as \u should be followed with 4 hexadecimal digits
            //dest.Replace("\u","\\u");
            printf("%u)>", dest.Length());
            puts(dest);
            fflush(stdout);
//            dest.Replace("<","&lt;");
//            dest.Replace(">","&gt;");
            ++numExtract;
        }
        // to avoid spending too much time when generating extracts on big files...
        if (ftell(fi)>32768)
            break;
        numLine++;
    }
    fclose(fi);
    return numExtract;
}

// beware we're on different threads here
bool MyWebServer::OnRequest(ExecContext& ctx)
{
    if (_wantExit == true)
        return false;
    if (ctx.IsURI("/kill"))
    {
        _wantExit = true;
        return true;
    }
    if (ctx.IsURI("/command.c"))
    {
        //return some json
        ctx.Print("{\"result\":\"ok\"}");
        return true;
    }
    if (ctx.IsURI("/open.c", 7))
    {
        int docId = atoi(ctx.GetURI()+8);
        if ((docId>0)&&(docId<indexArray[0].NbDocuments()))
        {
            char command[512];
            snprintf(command, sizeof(command),
                     "\"C:/Program Files/Sublime Text 2/sublime_text.exe\" \"%s%s\"",
                     indexArray[0].GetDocumentDir(docId),
                     indexArray[0].GetDocumentName(docId));
            //TODO: need to investigate why 'system' wont work here
            STARTUPINFOA si = { sizeof(si) };
            PROCESS_INFORMATION pi = { 0 };
            //envp = NULL; // Unused
            si.dwFlags = STARTF_USESTDHANDLES;// | STARTF_USESHOWWINDOW;
            //si.wShowWindow = SW_HIDE;
            bool res = CreateProcessA(NULL, command, NULL, NULL, TRUE,
                                      CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi) == 0;
        }
        ctx.Print("HTTP/1.1 200 OK\r\n"
                  "Content-Type: text/javascript\r\n\r\n");
        ctx.Print("{\"result\":\"ok\"}\r\n");
        return true;
    }
    if (ctx.IsURI("/explore.c", 10))
    {
        int docId = atoi(ctx.GetURI()+11);
        if ((docId>0)&&(docId<indexArray[0].NbDocuments()))
        {
            char command[512];
            snprintf(command, sizeof(command),"explorer.exe /e,/select,\"%s%s\"",
                     indexArray[0].GetDocumentDir(docId),
                     indexArray[0].GetDocumentName(docId));
            system(command);
        }
        ctx.Print("HTTP/1.1 200 OK\r\n"
                  "Content-Type: text/javascript\r\n\r\n");
        ctx.Print("{\"result\":\"ok\"}\r\n");
        return true;
    }
    if (ctx.IsURI("/search.c", 9))
    {
        ctx.Print("HTTP/1.1 200 OK\r\n"
                  "Content-Type: text/javascript\r\n\r\n");
        KeyValueIterator iter(ctx.GetURIParams());
        int start=0, count=10, sig=0;
        char            request[128];
        std::vector<size_t>  hits;
        while (iter.Iterate())
        {
            if (iter.IsKey("search"))
            {
                //if (!SStr::FastNICompare(lastRequest, iter.GetValue(), iter.GetValueSize()))
                {
                   // SStr(lastRequest, ARRAYSIZE(lastRequest)).Set(iter.GetValue(), iter.GetValueSize());
                    SStr(request, ARRAYSIZE(request)).Set(iter.GetValue(), iter.GetValueSize());
                    for (size_t numIndex=0; numIndex<indexArray.size(); numIndex++)
                    {
                        //lastHits.clear();
                        //indexArray[numIndex].FindDocuments(lastRequest, &lastHits);
                        hits.clear();
                        indexArray[numIndex].FindDocuments(request, &hits);
                    }                    
                }
            }
            else if (iter.IsKey("sig"))
                sig = iter.GetValueAsInt(0);
            else if (iter.IsKey("start"))
                start = iter.GetValueAsInt(0);
            else if (iter.IsKey("count"))
               count = iter.GetValueAsInt(10);
        }
        if (start>hits.size())
            start = 0;
        int end = start+count;
        if (end>hits.size())
            end = hits.size();
        ctx.Printf("{\"result\":\"ok\", \"sig\":\"%d\", \"start\":\"%d\", \"total\":\"%d\", \"count\":\"1\", \"hits\":[",
                   sig,
                   start,
                   hits.size());
        SString<260, PathStr> path;
        while (start<end)
        {            
            indexArray[0].GetDocumentPath(hits[start], path, path.Size());
            _extract extracts[4];
            size_t extractCount = getExtracts(path, request, extracts, 4);
            path.Replace("\\","\\\\");
            size_t size = FileUtil::getFileSize(path);
            SString<32> sizeText;
            if (size!=-1)
                sizeText.Format("%.2f kb", ((float)size)/((float)1024));

            bool res = ctx.Printf("{\"index\":\"%d\", \"id\":\"%d\", \"name\":\"%s\", \"path\":\"%s\", \"size\":\"%s\", \"extracts\":[",
                      start,
                      hits[start],
                      path.Filename(), path.Data(),
                       size==-1?"???":sizeText.Data(),
                      ((start+1)<end?',':' '));
            if ((extractCount>0)&&(true))
            {
                for (size_t index=0; index<extractCount; index++)
                {
                    if (!res) break;
                    if (res) res=ctx.Printf("{\"line\":%d, \"text\":\"", extracts[index].line);
                    if (res) res=ctx.Print(extracts[index].text);
                    if (res) res=ctx.Printf("\"}%c", ((index+1)<extractCount?',':' '));
                }
            }
            if (res) res=ctx.Printf("]}%c", ((start+1)<end?',':' '));
            if (!res)
                break;
    		start++;
        }
        ctx.Print("]}");
        return true;
    }
    if (ctx.IsURI("/status", 6))
    {
        ctx.Print("{result:\"ok\", status:\"indexing\", progress:\"50\"}");
        return true;
    }
    if (ctx.IsURI("/options", 9))
    {
        //if (ctx.IsPOST())
        {
            SString<256> buf;
            size_t len = ctx.Read(buf.Data(), 256);
            if (len>0)
            {

            }
            //ctx.Consume();//read remaining
            ctx.Print("{\"result\":\"ok\"}");
            return true;
        }
    }

    return false; //not processed, default will be to check for an existing (static) resource
}



void syntax()
{
    puts("owl_csearch: an application to index and search text files,");
    puts("\tindex will be stored and retrieved from current directory");
    puts("options:");
    puts("\t-h : this text");
    puts("\t-i <directory> : will index directory content recursively and exit");
    puts("\t-s <query> : search index for query and exit");
    puts("\t-w <directory> : set the www directory in webserver mode");
    puts("usage:");
    puts("\towl_csearch -i c:\\dev => will index all of c:\\dev content");
    puts("\towl_csearch -s fprintf => will output a list of file containing 'fprintf'");
    puts("\towl_csearch => will serve pages on http://127.0.0.1:6666 to query index from the browser");
}

/** destination dir is always current directory when indexing
Elapsed time to parse dirs : 33.12s
366 437 parsed files, for a 3 918 360 238 total bytes

with parsing
Elapsed time to parse dirs : 1427.18s
366437 parsed files, for a 3918359628 total bytes

with indexing(parsing+storage)
Elapsed time to parse dirs : 6518.60s
366473 parsed files, for a 3921983530 total bytes

*/
int main(int argc, char*argv[])
{
    SString<512, PathStr> webDirectory;
    SString<MAX_PATH, PathStr> currentDirectory;
    FileUtil::getCurrentDir(currentDirectory.Data(), currentDirectory.Size());
    currentDirectory.AppendTrailingSlash();
    webDirectory.Set(currentDirectory.Data());
    webDirectory.Append("./www/");
    webDirectory.Canonicalize();

    bool webDirSet = false;
    bool buildIndex = false;
    bool queryIndex = false;
    SString<256,SStr> queryString;
    if (argc>1)
    {
        for (int i=1; i<argc; i++)
        {
            char c = argv[i][0];
            if ((c=='-')||(c=='/')||(c=='\\'))
            {

                if ((argv[i][1]=='h')||(argv[i][1]=='?'))
                {
                    syntax();
                    return 0;
                }
                if (argv[i][1]=='w')
                {
                    ++i;
                    FileUtil::makeAbsolutePath(webDirectory.Data(), webDirectory.Size(), argv[i]);
                    if (!FileUtil::isDir(webDirectory))
                    {
                        fprintf(stderr, "couldn't find directory %s to web serve. Leaving.");
                        return -1;
                    }

                    webDirectory.AppendTrailingSlash();
                    webDirSet = true;
                }                
                if (argv[i][1]=='i')
                {
                    buildIndex = true;
                }
                if (argv[i][1]=='s')
                {
                    queryIndex = true;
                    ++i;
                    queryString.Set(argv[i]);
                }
            }
            else
            {
                if (FileUtil::isDir(argv[i]))
                {
                    SString<512, PathStr> dir(argv[i]);
                    FileUtil::makeAbsolutePath(dir.Data(), dir.Size(), argv[i]);
                    dir.RemoveTrailingSlash();
                    if (FileUtil::isDir(dir))
                    {
                        dir.AppendTrailingSlash();
                        indexDirArray.push_back(dir.Data());
                    }
                }
            }
        }
    }
    if (indexDirArray.size()==0)
    {
        SString<512, PathStr> dir;
        FileUtil::getCurrentDir(dir, dir.Size());
        dir.AppendTrailingSlash();
        indexDirArray.push_back(dir.Data());
    }

    if (buildIndex)
    {
        TriIndex        index;
        IndexBuilder    builder(&index);
        //MTIndexBuilder    builder(&index, 4);
        index.init();
        //Timer timer(true);
        for(size_t i=0; i<indexDirArray.size(); i++)
        {
            printf("Building index from dir %s... \n", indexDirArray[i].c_str());
            builder.indexDir(indexDirArray[i].c_str());
        }
        //printf("Elapsed time to parse dirs : %.2f\n", timer.asSeconds());
        printf("%u parsed files, for a %u bytes total.\n", builder.parsed, builder.parsedBytes);
        fflush(stdout);
        index.saveToDir(currentDirectory.Data());
        fflush(stdout);
        index.shutdown();
        return(0);
    }

    // else serve results from (possibly) multiple indices
    indexArray.resize(indexDirArray.size());
    for(size_t i=0; i<indexDirArray.size(); i++)
    {
        printf("Loading index from dir %s... \n", indexDirArray[i].c_str());
        fflush(stdout);
        clock_t t1 = clock();
        if (!indexArray[i].loadFromDir(indexDirArray[i].c_str()))
        {
            fprintf(stderr, "Couldn't load index: exiting.");
            fflush(stderr);
            return (-1);
        }
        clock_t t2 = clock();
        printf("... index read in %.2f s.\nReady to serve.\n", (float)(t2-t1)/(float)CLOCKS_PER_SEC);
        fflush(stdout);
    }
    //
    // from here i have a list of indices
    // and a webserver web directory
    if (queryIndex)
    {
        std::vector<size_t> hits;
        indexArray[0].FindDocuments(queryString, &hits);
        printf("%d hits found:\n", hits.size());
        SString<MAX_PATH, PathStr> path;
        for (int i=0; i<hits.size(); i++)
        {
            printf("\t%d) ", i+1);
            puts(indexArray[0].GetDocumentPath(hits[i], path.Data(), path.Size()));
        }
        fflush(stdout);
        return 0;
    }

    printf("On http://127.0.0.1:6666, serving pages from %s.\n", webDirectory);
    MyWebServer webServer(webDirectory, "6666");
    webServer.Start();
    while (!webServer.wantToExit())
    {
        sleepMs(1000);
    }
    webServer.Stop();

    for (size_t i=0; i<indexDirArray.size(); i++)
    {
        indexArray[i].shutdown();
    }

    return 0;
}

